import { MongoRepository } from 'typeorm';
import { Injectable, Logger } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { ConfigService } from '@nestjs/config';

import * as crypto from 'crypto';
import axios, { AxiosResponse } from 'axios';
import xml2js, { Parser } from 'xml2js';
import {
  CallbackParams,
  AuthResponse,
  WechatXmlMessage,
  WechatXmlParseResult,
  UserInfoResponse,
  AccessTokenResponse,
  FanInfoResponse,
  FansListResponse,
  MessageResult,
} from './wechat.types';

import { WechatFan } from './mongo/wechat-fan.entity'; // MongoDB 实体
@Injectable()
export class WechatService {
  private accessToken?: string;
  private accessTokenExpires: number = 0;

  constructor(
    private readonly configService: ConfigService,
    @InjectRepository(WechatFan, 'mongo')
    private readonly wechatFanRepository: MongoRepository<WechatFan>,
  ) {}
  private readonly logger = new Logger(WechatService.name);
  /**
   * 获取微信访问令牌
   */
  async getAccessToken(): Promise<string> {
    // 检查是否已有有效的访问令牌
    if (this.accessToken && this.accessTokenExpires > Date.now()) {
      return this.accessToken;
    }

    try {
      const appid = this.configService.get<string>('wechat.appid');
      const secret = this.configService.get<string>('wechat.secret');

      if (!appid || !secret) {
        throw new Error('微信配置缺失，请检查配置文件');
      }

      const payload: Record<string, string | boolean> = {
        grant_type: 'client_credential',
        appid,
        secret,
        force_refresh: false,
      };

      const response: AxiosResponse<AccessTokenResponse> = await axios.post(
        'https://api.weixin.qq.com/cgi-bin/stable_token',
        payload,
      );

      if (response.data && response.data.access_token) {
        this.accessToken = response.data.access_token;
        this.logger.log(`获取微信访问令牌成功: ${this.accessToken}`);
        // 设置过期时间，提前5分钟过期以确保安全
        this.accessTokenExpires =
          Date.now() + (response.data.expires_in - 300) * 1000;
        return this.accessToken;
      } else {
        throw new Error(
          `获取微信访问令牌失败: ${JSON.stringify(response.data)}`,
        );
      }
    } catch (error) {
      this.logger.error(`获取微信访问令牌出错: ${error}`);
      throw error;
    }
  }

  /**
   * 发送文本消息给指定粉丝
   * @param openid 粉丝的openid
   * @param content 消息内容
   */
  async sendTextMessage(
    openid: string,
    content: string,
  ): Promise<MessageResult> {
    try {
      const accessToken = await this.getAccessToken();
      const url = `https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token=${accessToken}`;

      const data = {
        touser: openid,
        msgtype: 'text',
        text: {
          content,
        },
      };

      const response: AxiosResponse<{
        errcode: number;
        errmsg: string;
        msgid?: number;
      }> = await axios.post(url, data);

      if (response.data && response.data.errcode === 0) {
        return {
          success: true,
          message: '消息发送成功',
          msgid: response.data.msgid,
        };
      } else {
        this.logger.error(`发送消息失败: ${JSON.stringify(response.data)}`);
        return { success: false, message: `发送失败: ${response.data.errmsg}` };
      }
    } catch (error) {
      this.logger.error(`发送消息出错: ${error}`);
      throw error;
    }
  }

  /**
   * 发送模板消息给指定粉丝
   * @param openid 粉丝的openid
   * @param templateId 模板ID
   * @param data 模板数据
   * @param url 点击模板消息后跳转的链接
   */
  async sendTemplateMessage(
    openid: string,
    templateId: string,
    data: Record<string, { value: string; color?: string }>,
    url?: string,
  ): Promise<MessageResult> {
    try {
      const accessToken = await this.getAccessToken();
      const apiUrl = `https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=${accessToken}`;

      const postData: Record<string, any> = {
        touser: openid,
        template_id: templateId,
        data,
      };

      if (url) {
        postData.url = url;
      }

      const response: AxiosResponse<{
        errcode: number;
        errmsg: string;
        msgid?: number;
      }> = await axios.post(apiUrl, postData);

      if (response.data && response.data.errcode === 0) {
        return {
          success: true,
          message: '模板消息发送成功',
          msgid: response.data.msgid,
        };
      } else {
        this.logger.error(`发送模板消息失败: ${JSON.stringify(response.data)}`);
        return { success: false, message: `发送失败: ${response.data.errmsg}` };
      }
    } catch (error) {
      this.logger.error(`发送模板消息出错: ${error}`);
      throw error;
    }
  }

  /**
   * 获取粉丝列表
   */
  async getFansList(nextOpenid?: string): Promise<FansListResponse> {
    try {
      const accessToken = await this.getAccessToken();
      const url = `https://api.weixin.qq.com/cgi-bin/user/get?access_token=${accessToken}${nextOpenid ? `&next_openid=${nextOpenid}` : ''}`;

      const response: AxiosResponse<FansListResponse> = await axios.get(url);

      if (response.data && response.data.data) {
        return response.data;
      } else {
        this.logger.error(`获取粉丝列表失败: ${JSON.stringify(response.data)}`);
        throw new Error(`获取粉丝列表失败: ${response.data.errmsg}`);
      }
    } catch (error) {
      this.logger.error(`获取粉丝列表出错: ${error}`);
      throw error;
    }
  }

  /**
   * 获取粉丝详细信息
   * @param openid 粉丝的openid
   */
  async getFanInfo(openid: string): Promise<FanInfoResponse> {
    try {
      const accessToken = await this.getAccessToken();
      const url = `https://api.weixin.qq.com/cgi-bin/user/info?access_token=${accessToken}&openid=${openid}&lang=zh_CN`;

      const response: AxiosResponse<FanInfoResponse> = await axios.get(url);

      if (response.data && !response.data.errcode) {
        return response.data;
      } else {
        this.logger.error(`获取粉丝信息失败: ${JSON.stringify(response.data)}`);
        throw new Error(`获取粉丝信息失败: ${response.data.errmsg}`);
      }
    } catch (error) {
      this.logger.error(`获取粉丝信息出错: ${error}`);
      throw error;
    }
  }

  /**
   * 验证微信签名
   * @param params 包含timestamp、nonce、signature、echostr的回调参数对象
   * @returns 验证成功返回echostr，验证失败或出错返回空字符串
   */
  verifyWechatSignature(params: CallbackParams): string {
    try {
      // 将token、timestamp、nonce三个参数进行字典序排序
      const token = this.configService.get<string>('wechat.token');
      if (!token) {
        throw new Error('微信token配置缺失');
      }

      const { timestamp, nonce, signature, echostr } = params;
      const arr: string[] = [token, timestamp, nonce];
      arr.sort();

      // 将三个参数字符串拼接成一个字符串进行sha1加密
      const tempStr = arr.join('');
      const sha1 = crypto.createHash('sha1');
      sha1.update(tempStr);
      const hashcode = sha1.digest('hex');

      this.logger.log(
        `handle/GET func: hashcode, signature: ${hashcode}, ${signature}`,
      );

      // 对比签名
      return hashcode === signature ? echostr : '';
    } catch (error) {
      this.logger.error('微信接入验证出错:', error);
      return '';
    }
  }

  /**
   * 解析微信回调 XML 为 JSON（明确类型，无 any）
   * @param xml 微信回调的 XML 字符串
   * @returns 解析后的 JSON 对象（结构：{ ToUserName, FromUserName, ... }）
   */
  async parseXmlToJson(xml: string): Promise<WechatXmlMessage> {
    const parser: Parser = new xml2js.Parser({
      explicitArray: false, // 不将单元素转为数组（确保字段值为 string，而非 string[]）
      trim: true, // 去除字段值首尾空格（避免 XML 中换行/空格导致的脏数据）
      ignoreAttrs: true, // 忽略 XML 属性（微信回调 XML 无关键属性，可减少冗余）
    });

    // 关键：用 WechatXmlParseResult 替代 any，明确解析结果类型
    const parseResult: WechatXmlParseResult = (await parser.parseStringPromise(
      xml,
    )) as WechatXmlParseResult;

    // 验证解析结果是否包含 xml 节点（避免非法 XML 导致的后续错误）
    if (!parseResult || !parseResult.xml) {
      throw new Error(
        `XML 解析失败：未找到根节点 <xml>，原始XML：${xml.slice(0, 200)}`,
      );
    }

    // 验证核心字段是否存在（确保是合法的微信回调消息）
    const requiredFields = [
      'ToUserName',
      'FromUserName',
      'CreateTime',
      'MsgType',
    ];
    const missingFields = requiredFields.filter(
      (field) =>
        !parseResult.xml[field] || typeof parseResult.xml[field] !== 'string',
    );
    if (missingFields.length > 0) {
      throw new Error(
        `XML 消息缺少核心字段：${missingFields.join(', ')}，解析结果：${JSON.stringify(parseResult.xml)}`,
      );
    }

    // 返回 xml 节点下的内容（类型为 WechatXmlMessage，无 any）
    return parseResult.xml;
  }

  /**
   * 生成被动回复的XML（文本消息示例）
   * @param originalMessage 原始消息对象
   * @param content 回复内容
   * @returns 回复的XML字符串
   */
  generateReplyXml(
    originalMessage: Record<string, string>,
    content: string,
  ): string {
    const timestamp = Date.now().toString();
    return `
      <xml>
        <ToUserName><![CDATA[${originalMessage.FromUserName}]]></ToUserName>
        <FromUserName><![CDATA[${originalMessage.ToUserName}]]></FromUserName>
        <CreateTime>${timestamp}</CreateTime>
        <MsgType><![CDATA[text]]></MsgType>
        <Content><![CDATA[${content}]]></Content>
      </xml>
    `.trim(); // 去除多余空格，避免XML解析错误
  }

  /**
   * 查询当前微信号的绑定信息
   * @param code 作为换取access_token的票据
   * @returns 绑定信息（未绑定时返回新创建的记录）
   */
  async checkUserPhone(code: string): Promise<WechatFan | null> {
    try {
      const appid = this.configService.get<string>('wechat.appid');
      const secret = this.configService.get<string>('wechat.secret');

      if (!appid || !secret) {
        throw new Error('微信配置缺失，请检查appid和secret');
      }

      // 使用 code 获取用户 openid（通过微信网页授权接口）
      const authUrl = `https://api.weixin.qq.com/sns/oauth2/access_token?appid=${appid}&secret=${secret}&code=${code}&grant_type=authorization_code`;

      // 调用微信接口获取openid
      const authResponse: AxiosResponse<AuthResponse> =
        await axios.get(authUrl);

      if (!authResponse || authResponse.status !== 200) {
        throw new Error(`微信授权接口调用失败: ${authResponse?.statusText}`);
      }

      const { openid, unionid, access_token, errcode, errmsg } =
        authResponse.data;

      // 处理微信接口返回的错误
      if (errcode) {
        throw new Error(`微信授权失败 [${errcode}]: ${errmsg}`);
      }

      if (!openid) {
        throw new Error('未能获取到用户openid');
      }
      console.log("========= siisi =========\n", { openid, unionid, access_token, errcode, errmsg });
      // 查询当前微信号的绑定信息
      let user: WechatFan | null = await this.wechatFanRepository.findOne({
        where: { openid },
        select: [
          '_id',
          'openid',
          'phone',
          'headimgurl',
          'nickname',
          'unionid',
          'role',
        ],
      });

      // 如果未绑定，先创建新的绑定记录
      if (!user) {
        if (!access_token) {
          throw new Error('获取用户access_token失败');
        }

        // 使用access_token和openid获取用户详细信息（头像、昵称等）
        const userInfoUrl = `https://api.weixin.qq.com/sns/userinfo?access_token=${access_token}&openid=${openid}&lang=zh_CN`;
        const userInfoResponse: AxiosResponse<UserInfoResponse> =
          await axios.get(userInfoUrl);

        if (!userInfoResponse || userInfoResponse.status !== 200) {
          throw new Error(`获取用户信息失败: ${userInfoResponse?.statusText}`);
        }

        const {
          nickname,
          headimgurl,
          errcode: userInfoErrCode,
          errmsg: userInfoErrMsg,
        } = userInfoResponse.data;
        console.log("========= siisi =========\n", nickname, headimgurl,);

        if (userInfoErrCode) {
          throw new Error(
            `获取用户信息失败 [${userInfoErrCode}]: ${userInfoErrMsg}`,
          );
        }

        this.logger.log(`用户未绑定，创建新绑定记录: ${openid}`);

        // 创建新用户记录
        const newUserDoc = {
          openid: openid,
          nickname: nickname || null,
          headimgurl: headimgurl || null,
          unionid: unionid || null,
          createdAt: new Date(),
          updatedAt: new Date(),
        } as WechatFan;
        await this.wechatFanRepository.insert(newUserDoc);
        user =
          (await this.wechatFanRepository.findOne({
            where: { openid: openid },
          })) || null;
      }

      return user;
    } catch (error) {
      this.logger.error(`查询微信号绑定状态失败: ${code}`, error);
      throw error;
    }
  }

  /**
   * 绑定微信和用户手机号
   * @param phone 手机号
   * @param role 用户角色
   * @param openid 微信用户唯一标识
   * @returns 绑定结果
   */
  async bindUserPhone(
    phone: string,
    role: string,
    openid: string,
  ): Promise<{
    success: boolean;
    message: string;
    data?: { openid: string; phone: string };
  }> {
    try {
      // 确保accessToken有效
      await this.getAccessToken();

      // 检查是否已绑定该手机号
      const existingUserByPhone = await this.wechatFanRepository.findOne({
        where: { phone, openid: { $ne: openid } },
      });

      if (existingUserByPhone) {
        this.logger.warn(`手机号已被绑定: ${phone}`);
        return {
          success: false,
          message: '该手机号已被其他微信账号绑定',
        };
      }

      // 检查该微信是否已绑定其他手机号
      const existingUserByOpenid = await this.wechatFanRepository.findOne({
        where: { openid },
      });

      if (existingUserByOpenid) {
        // 更新已有记录
        existingUserByOpenid.phone = phone;
        existingUserByOpenid.role = role;
        existingUserByOpenid.updatedAt = new Date();
        await this.wechatFanRepository.save(existingUserByOpenid);

        this.logger.log(`微信账号更新绑定手机号: ${openid} -> ${phone}`);
        return {
          success: true,
          message: '手机号绑定成功',
          data: { openid, phone },
        };
      } else {
        // 创建新的绑定记录
        const newWechatFan = this.wechatFanRepository.create({
          openid,
          phone,
          role,
          createdAt: new Date(),
          updatedAt: new Date(),
        });

        await this.wechatFanRepository.save(newWechatFan);

        this.logger.log(`微信账号绑定新手机号: ${openid} -> ${phone}`);
        return {
          success: true,
          message: '手机号绑定成功',
          data: { openid, phone },
        };
      }
    } catch (error) {
      this.logger.error(`绑定用户手机失败: ${phone}, ${openid}`, error);
      throw error;
    }
  }
}
